/* * Lilith - a log event viewer. * Copyright (C) 2007-2014 Joern Huxhorn * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package de.huxhorn.lilith.swing; import de.huxhorn.sulky.swing.GraphicsUtilities; import de.huxhorn.sulky.swing.filters.ColorTintFilter; import java.awt.AlphaComposite; import java.awt.Color; import java.awt.Dimension; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Insets; import java.awt.Point; import java.awt.Rectangle; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import java.awt.event.MouseEvent; import java.awt.image.BufferedImage; import java.awt.image.BufferedImageOp; import java.awt.image.ConvolveOp; import java.awt.image.Kernel; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.List; import java.util.StringTokenizer; import javax.swing.JComponent; import javax.swing.Timer; import javax.swing.ToolTipManager; import javax.swing.UIManager; import javax.swing.event.MouseInputAdapter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; // TODO: get/setMouseHandling/MouseInputMode // TODO: setVersionHeight(versionHeight); // TODO: setVersionString(versionString); // TODO: correct versionHeight if string would be outside background. // TODO: VersionString centered to bg/scroll. // TODO: relative ScrollAreas (values given as % of backgroundImage) // TODO: Handle errors in Image-Loading // TODO: offscreenImage hoechstens so gross wie die size / nicht gesamten bg malen // TODO: paint background-color for rest of component (not only behind bg-image) // TODO: scroll-area defined by object-array containing icons and strings... // TODO: transient attributes // TODO: serialVersion // TODO: use ResourceSupport /** * <code>AboutPanel</code> is a component which has a background-image and a * rectangle in which a given text is scrolling (the scroll-area). You may also * specify an Image (e.g. a png-file with alpha-channel) that is drawn before * the scroll-text itself. An optional version-string may be given that will be * painted centered relative to the scroll-area. * * @author Joern Huxhorn */ public class AboutPanel extends JComponent { private final Logger logger = LoggerFactory.getLogger(AboutPanel.class); public static final String BACKGROUND_IMAGE_RESOURCE = "background.png"; public static final String ABOUT_IMAGE_RESOURCE = "about.png"; public static final String TEXT_RESOURCE_PREFIX = "about."; public static final String SCROLL_TEXT_RESOURCE = TEXT_RESOURCE_PREFIX + "scroll.text"; public static final String VERSION_TEXT_RESOURCE = TEXT_RESOURCE_PREFIX + "version.text"; public static final String VERSION_HEIGHT_RESOURCE = TEXT_RESOURCE_PREFIX + "version.height"; public static final String SCROLL_AREA_RESOURCE_BASE = TEXT_RESOURCE_PREFIX + "scroll.area."; public static final String SCROLL_AREA_X_RESOURCE = SCROLL_AREA_RESOURCE_BASE + "x"; public static final String SCROLL_AREA_Y_RESOURCE = SCROLL_AREA_RESOURCE_BASE + "y"; public static final String SCROLL_AREA_WIDTH_RESOURCE = SCROLL_AREA_RESOURCE_BASE + "width"; public static final String SCROLL_AREA_HEIGHT_RESOURCE = SCROLL_AREA_RESOURCE_BASE + "height"; public static final String SCROLL_AREA_TOOLTIP_TEXT_RESOURCE = SCROLL_AREA_RESOURCE_BASE + "tooltip.text"; public static final String TEXT_RESOURCE_BUNDLE_RESOURCE = "TextResources"; public static final int MOUSE_DISABLED = 0; public static final int MOUSE_COMPONENT = 1; public static final int MOUSE_SCROLLAREA = 2; public static final int MOUSE_BACKGROUND = 3; //private static final int SCROLL_SLEEP_TIME = 50; private static final int SCROLL_PIXELS = 1; private BufferedImage backgroundImage; private BufferedImage aboutImage; private FontMetrics fontMetrics; private Insets insets; private Dimension size; private Dimension preferredSize; private Point offscreenOffset; private String[] scrollLines; private String versionText; private String scrollAreaToolTipText; private int versionHeight; private int scrollPosition; private int maxScrollPosition; private int minScrollPosition; private Rectangle maxScrollArea; private Rectangle backgroundImageArea; private Rectangle translatedBackgroundImageArea; private Rectangle translatedScrollArea; private Rectangle scrollArea; private Rectangle paintArea; private BufferedImage offscreenImage; private BufferedImage scrollImage; private boolean scrolling; private int mouseEventHandling = MOUSE_BACKGROUND; private boolean debug; private Timer timer; /** * Creates a new <code>AboutPanel</code> initialized with the given parameters. * * @param backgroundImageUrl The URL to the Background-Image of the * AboutPanel. This parameter is mandatory. * @param scrollArea The Rectangle inside the background-image where * scrolling should take place. This parameter is optional. If it's null * then the scroll-area is set to (0, 0, background.width, * background.height). * @param scrollText The text that will be scrolled. * @throws java.io.IOException If receiving content from backgroundImageUrl fails */ public AboutPanel(URL backgroundImageUrl, Rectangle scrollArea, String scrollText) throws IOException { this(backgroundImageUrl, scrollArea, scrollText, null, null, -1); } public boolean isDebug() { return debug; } public void setDebug(boolean debug) { this.debug = debug; } /** * Creates a new <code>AboutPanel</code> initialized with the given parameters. * * @param backgroundImageUrl The URL to the Background-Image of the * AboutPanel. This parameter is mandatory. * @param scrollArea The Rectangle inside the background-image where * scrolling should take place. This parameter is optional. If it's null * then the scroll-area is set to (0, 0, background.width, * background.height). * @param scrollText The text to be scrolled. * @param versionText The String describing the version of the program. * It is painted centered to the scroll-rectangle at the specified height. * This parameter is optional. * @param versionHeight The height at which the version-string is * supposed to be painted. This parameter is optional but should be given * a correct value if versionText!=null.. * @throws IOException if loading the images failed. */ public AboutPanel(URL backgroundImageUrl, Rectangle scrollArea, String scrollText, String versionText, int versionHeight) throws IOException { this(backgroundImageUrl, scrollArea, scrollText, null, versionText, versionHeight); } /** * Creates a new <code>AboutPanel</code> initialized with the given parameters. * * @param backgroundImageUrl The URL to the Background-Image of the * AboutPanel. This parameter is mandatory. * @param scrollArea The Rectangle inside the background-image where * scrolling should take place. This parameter is optional. If it's null * then the scroll-area is set to (0, 0, background.width, * background.height). * @param scrollText The text to be scrolled. * @param imageUrl The URL to the Image that will be painted at the * start of the scroll-area. This parameter is optional. * @param versionText The String describing the version of the program. * It is painted centered to the scroll-rectangle at the specified height. * This parameter is optional. * @param versionHeight The height at which the version-string is * supposed to be painted. This parameter is optional but should be given * a correct value if versionText!=null.. * @throws IOException if loading the images failed. */ public AboutPanel(URL backgroundImageUrl, Rectangle scrollArea, String scrollText, URL imageUrl, String versionText, int versionHeight) throws IOException { this(); if(backgroundImageUrl == null) { throw new NullPointerException("backgroundImageUrl must not be null!"); } if(scrollText == null) { throw new NullPointerException("scrollText must not be null!"); } init(backgroundImageUrl, scrollArea, scrollText, imageUrl, versionText, versionHeight); } public AboutPanel() { ActionListener timerListener = new TimerActionListener(); timer = new Timer(10, timerListener); //this.resourceSupport=new ResourceSupport(this); initAttributes(); addPropertyChangeListener(new AboutPropertyChangeListener()); addComponentListener(new AboutComponentListener()); setFont(null);// initializes to Label.font AboutMouseInputListener mouseInputListener = new AboutMouseInputListener(); addMouseListener(mouseInputListener); addMouseMotionListener(mouseInputListener); setScrolling(false); } private void init(URL backgroundImageUrl, Rectangle scrollArea, String scrollText, URL imageUrl, String versionText, int versionHeight) throws IOException { if(logger.isDebugEnabled()) logger.debug("init called with following arguments: backgroundImageUrl={}, scrollArea={}, scrollText={}, imageUrl={}, versionText={}, versionHeight={}", backgroundImageUrl, scrollArea, scrollText, imageUrl, versionText, versionHeight); setBackgroundImage(backgroundImageUrl); setScrollArea(scrollArea); setAboutImage(imageUrl); this.versionText = versionText; this.versionHeight = versionHeight; setScrollText(scrollText); } private void initAttributes() { preferredSize = new Dimension(); offscreenOffset = new Point(); backgroundImageArea = new Rectangle(); translatedScrollArea = new Rectangle(); translatedBackgroundImageArea = new Rectangle(); scrollArea = new Rectangle(); paintArea = new Rectangle(); insets = getInsets(); } public void setScrollText(String scrollText) { StringTokenizer st = new StringTokenizer(scrollText, "\n", true); List<String> lines = new ArrayList<>(st.countTokens() / 2); String prevToken = null; while(st.hasMoreTokens()) { String token = st.nextToken(); if("\n".equals(token)) { if(prevToken != null && !"\n".equals(prevToken)) { lines.add(prevToken); } else { lines.add(""); } } prevToken = token; } if(prevToken != null && !"\n".equals(prevToken)) { lines.add(prevToken); } String[] loScrollLines = new String[lines.size()]; loScrollLines = lines.toArray(loScrollLines); setScrollLines(loScrollLines); } protected void setScrollLines(String[] scrollLines) { if(scrollLines == null) { NullPointerException ex = new NullPointerException("scrollLines must not be null!"); if(logger.isDebugEnabled()) { logger.debug("Parameter 'scrollLines' of method 'setScrollLines' must not be null!", ex); } throw ex; } this.scrollLines = scrollLines.clone(); flushScrollImage(); } /** * Sets the backgroundImage attribute of the <code>AboutPanel</code> object * * @param imageUrl the image to be used as background. * @throws IOException if loading of image fails. */ public void setBackgroundImage(URL imageUrl) throws IOException { setBackgroundImage(GraphicsUtilities.loadCompatibleImage(imageUrl)); } /** * Sets the backgroundImage attribute of the <code>AboutPanel</code> object * * @param backgroundImage The new backgroundImage value */ public void setBackgroundImage(BufferedImage backgroundImage) { if(this.backgroundImage != null) { this.backgroundImage.flush(); this.backgroundImage = null; } this.backgroundImage = backgroundImage; updateBackgroundAttributes(); } public void setAboutImage(URL imageUrl) throws IOException { setAboutImage(GraphicsUtilities.loadCompatibleImage(imageUrl)); } public void setAboutImage(BufferedImage aboutImage) { if(this.aboutImage != null) { this.aboutImage.flush(); this.aboutImage = null; } this.aboutImage = aboutImage; flushScrollImage(); } /** * Sets the scrollArea attribute of the <code>AboutPanel</code> object * * @param scrollArea The new scrollArea value */ public void setScrollArea(Rectangle scrollArea) { if(scrollArea != null) { maxScrollArea = backgroundImageArea.intersection(scrollArea); } else { maxScrollArea = (Rectangle) backgroundImageArea.clone(); } minScrollPosition = -maxScrollArea.height; calculateAttributes(); flushScrollImage(); } /** * Description of the Method */ private void flushScrollImage() { if(scrollImage != null) { if(logger.isInfoEnabled()) logger.info("Flushing ScrollImage"); scrollImage.flush(); scrollImage = null; } setScrollPosition(minScrollPosition); } /** * Description of the Method */ private void flushOffscreenImage() { if(offscreenImage != null) { if(logger.isInfoEnabled()) logger.info("Flushing OffscreenImage"); offscreenImage.flush(); offscreenImage = null; } } /** * Description of the Method */ private void updateBackgroundAttributes() { backgroundImageArea.x = 0; backgroundImageArea.y = 0; backgroundImageArea.width = backgroundImage.getWidth(); backgroundImageArea.height = backgroundImage.getHeight(); calculatePreferredSize(); if(maxScrollArea != null) { maxScrollArea = maxScrollArea.intersection(backgroundImageArea); } else { maxScrollArea = (Rectangle) backgroundImageArea.clone(); } flushOffscreenImage(); flushScrollImage(); repaint(); } /** * Sets the ToolTipText that will appear if the user moves the mouse over the * scroll-area of this component. * * @param toolTipText The new ScrollAreaToolTipText value */ public void setScrollAreaToolTipText(String toolTipText) { scrollAreaToolTipText = toolTipText; } /** * Gets the ScrollAreaToolTipText attribute of the <code>AboutPanel</code> * object * * @return The ScrollAreaToolTipText value */ public String getScrollAreaToolTipText() { return scrollAreaToolTipText; } /** * This method returns ScrollAreaToolTipText if the point of the <code>MouseEvent</code> * is inside the scroll-area and <code>null</code> otherwise. * * It's needed by the <code>ToolTipManager</code>. * * @param evt a <code>MouseEvent</code>. * @return The toolTipText value for the <code>ToolTipManager</code>. */ public String getToolTipText(MouseEvent evt) { if(handleMouseEvent(evt)) { return scrollAreaToolTipText; } return null; } protected boolean handleMouseEvent(MouseEvent evt) { Rectangle loArea = null; if(mouseEventHandling == MOUSE_BACKGROUND) { loArea = translatedBackgroundImageArea; } else if(mouseEventHandling == MOUSE_SCROLLAREA) { loArea = translatedScrollArea; } else if(mouseEventHandling == MOUSE_DISABLED) { return false; } Point loPoint = evt.getPoint(); if(loArea == null) { // -> default: MOUSE_COMPONENT return contains(loPoint); } if(loArea.contains(loPoint)) { // MOUSE_BACKGROUND / MOUSE_SCROLLAREA return true; } return false; } /** * Increases the ScrollPosition by SCROLL_PIXELS. This method is called by the * scroll-thread and calls <code>setScrollPosition</code>, therefore causing a * repaint of the scroll-area.. * * @see #setScrollPosition */ protected void increaseScrollPosition() { setScrollPosition(scrollPosition + SCROLL_PIXELS); } /** * Sets the scrollPosition attribute of the <code>AboutPanel</code> object. The * value will be corrected according Minimum- and MaximumScrollPosition. * Changing the scroll-position will result in a repaint of the scroll-area. * * @param scrollPosition The new scrollPosition value. This value indicates * the height-offset of the scroll-area. * @see #getMinimumScrollPosition * @see #getMaximumScrollPosition */ public void setScrollPosition(int scrollPosition) { if(scrollPosition > maxScrollPosition) { int remainder = scrollPosition % maxScrollPosition; scrollPosition = minScrollPosition + remainder; } else if(scrollPosition < minScrollPosition) { int remainder = scrollPosition % minScrollPosition; scrollPosition = maxScrollPosition + remainder; } if(this.scrollPosition != scrollPosition) { this.scrollPosition = scrollPosition; repaintScrollArea(); } } /** * Gets the ScrollPosition attribute of the <code>AboutPanel</code> object * * @return this value indicates the height-offset of the scroll-area. */ public int getScrollPosition() { return scrollPosition; } /** * Gets the MinimumScrollPosition attribute of the <code>AboutPanel</code> * object. It's value is the negated value of the scroll-area-height. * * @return The MinimumScrollPosition value */ public int getMinimumScrollPosition() { return minScrollPosition; } /** * Gets the MaximumScrollPosition attribute of the <code>AboutPanel</code> * object. It's value is the height needed for all lines of text plus (if * available) the height of the image with an additional empty line. * * @return The MaximumScrollPosition value */ public int getMaximumScrollPosition() { return maxScrollPosition; } /** * This method creates the offscreen-image when needed (when called for the * first time or recreated because of a changed font) and updates it on * subsequent calls by calling <code>updateOffscreenImage()</code>. */ private void processOffscreenImage() { Graphics2D g; if(offscreenImage == null) { if(logger.isInfoEnabled()) logger.info("Creating offscreen-image"); boolean opaque = false; if(isOpaque()) { offscreenImage = GraphicsUtilities .createOpaqueCompatibleImage(backgroundImageArea.width, backgroundImageArea.height); opaque = true; } else { offscreenImage = GraphicsUtilities .createTranslucentCompatibleImage(backgroundImageArea.width, backgroundImageArea.height); } g = (Graphics2D) offscreenImage.getGraphics(); if(opaque) { g.setColor(getBackground()); g.fillRect(backgroundImageArea.x, backgroundImageArea.y, backgroundImageArea.width, backgroundImageArea.height); } g.drawImage(backgroundImage, 0, 0, null); if(versionText != null) { // draw version-text... g.setColor(getForeground()); g.drawString(versionText, maxScrollArea.x + (maxScrollArea.width - fontMetrics.stringWidth(versionText)) / 2, versionHeight); } } else { g = (Graphics2D) offscreenImage.getGraphics(); } g.setFont(getFont()); drawScrollArea(g); g.dispose(); } /** * Updates the offscreen-image to represent the current scroll-position. It * calls <code>initScrollImage()</code>. * * @param g <code>Graphics</code>-object */ private void drawScrollArea(Graphics2D g) { initScrollImage(); // only draw in the scroll-area g.setClip(scrollArea.x, scrollArea.y, scrollArea.width, scrollArea.height); // clear background for transparent bg-images g.setColor(getBackground()); g.fillRect(scrollArea.x, scrollArea.y, scrollArea.width, scrollArea.height); // draw background-image g.drawImage(backgroundImage, 0, 0, this); // redraw version-text if available. if(versionText != null) { g.setColor(getForeground()); g.drawString(versionText, maxScrollArea.x + (maxScrollArea.width - fontMetrics.stringWidth(versionText)) / 2, versionHeight); } // draw proper part of precalculated scroll-image. g.drawImage(scrollImage, scrollArea.x, scrollArea.y - scrollPosition, this); if(debug) { g.setColor(Color.YELLOW); g.drawRect(scrollArea.x, scrollArea.y, scrollArea.width - 1, scrollArea.height - 1); } } /** * Initializes the scroll-image if needed. The scroll-image is as high as * needed to contain all the scroll-lines and (if available) the image. */ private void initScrollImage() { int fontHeight = fontMetrics.getHeight(); maxScrollPosition = fontHeight * (scrollLines.length); int additionalImageOffset = 0; int imageWidth = 0; if(aboutImage != null) { imageWidth = aboutImage.getWidth(); additionalImageOffset = aboutImage.getHeight() + 2 * fontHeight; maxScrollPosition = maxScrollPosition + additionalImageOffset; } if(scrollImage != null && scrollImage.getHeight() != maxScrollPosition) { flushScrollImage(); } if(scrollImage == null) { int maxWidth = imageWidth + 2 * fontHeight; if(logger.isInfoEnabled()) logger.info("imageWidth={}, maxWidth={}", imageWidth, maxWidth); for(String scrollLine : scrollLines) { int curWidth = fontMetrics.stringWidth(scrollLine); if(curWidth > maxWidth) { maxWidth = curWidth; } } if(maxWidth > maxScrollArea.width) { if(logger.isInfoEnabled()) logger.info("maxWidth={} != maxScrollArea=", maxWidth, maxScrollArea); maxWidth = maxScrollArea.width; } scrollArea.x = maxScrollArea.x + (maxScrollArea.width - maxWidth) / 2; scrollArea.y = maxScrollArea.y; scrollArea.width = maxWidth; scrollArea.height = maxScrollArea.height; scrollImage = GraphicsUtilities.createTranslucentCompatibleImage(scrollArea.width, maxScrollPosition); Color foreground = getForeground(); Graphics2D g; g = (Graphics2D) scrollImage.getGraphics(); g.setFont(getFont()); if(aboutImage != null) { g.drawImage(aboutImage, (((scrollArea.width - imageWidth) / 2)), fontHeight, null); } g.setColor(foreground); int y = fontMetrics.getAscent() + additionalImageOffset; for(String line : scrollLines) { g.drawString(line, (scrollArea.width - fontMetrics.stringWidth(line)) / 2, y); y += fontHeight; } g.dispose(); BufferedImage copy = GraphicsUtilities.createCompatibleCopy(scrollImage); BufferedImageOp filter; final int blurSize = 10; filter = getGaussianBlurFilter(blurSize, false); scrollImage = filter.filter(scrollImage, null); filter = getGaussianBlurFilter(blurSize, true); scrollImage = filter.filter(scrollImage, null); filter = new ColorTintFilter(Color.GREEN, 1.0f); scrollImage = filter.filter(scrollImage, null); g = (Graphics2D) scrollImage.getGraphics(); g.setComposite(AlphaComposite.SrcOver); g.drawImage(copy, 0, 0, null); if(debug) { g.setColor(Color.RED); g.drawRect(0, 0, scrollImage.getWidth() - 1, scrollImage.getHeight() - 1); g.setColor(Color.GREEN); g.drawRect((((scrollArea.width - imageWidth) / 2)), fontHeight, aboutImage.getWidth(), aboutImage.getHeight()); } g.dispose(); copy.flush(); } } public static ConvolveOp getGaussianBlurFilter(int radius, boolean horizontal) { final Logger logger = LoggerFactory.getLogger(AboutPanel.class); if(radius < 1) { throw new IllegalArgumentException("Radius must be >= 1"); } int size = radius * 2 + 1; float[] data = new float[size]; float sigma = radius / 3.0f; float twoSigmaSquare = 2.0f * sigma * sigma; float sigmaRoot = (float) Math.sqrt(twoSigmaSquare * Math.PI); float total = 0.0f; for(int i = -radius; i <= radius; i++) { float distance = i * i; int index = i + radius; data[index] = (float) Math.exp(-distance / twoSigmaSquare) / sigmaRoot; total += data[index]; } for(int i = 0; i < data.length; i++) { data[i] /= total; if(logger.isDebugEnabled()) logger.debug("data[{}]={}", i, data[i]); } Kernel kernel = null; if(horizontal) { kernel = new Kernel(size, 1, data); } else { kernel = new Kernel(1, size, data); } return new ConvolveOp(kernel, ConvolveOp.EDGE_NO_OP, null); } /** * Sets the font attribute of the <code>AboutPanel</code> object. Setting it * will result in the recreation of all buffers. The font can even be safely * changed while the component is visible. It will be used for the version- and * scroll-text. * * If the parameter is <code>null</code> then <code>UIManager.getFont( "Label.font" )</code> * will be used. * * @param newFont The new font value. */ public void setFont(Font newFont) { if(newFont == null) { newFont = UIManager.getFont("Label.font"); } if(newFont != null && !newFont.equals(getFont())) { super.setFont(newFont); fontMetrics = getFontMetrics(newFont); flushScrollImage(); } } /** * Paints this component. * * @param graphics <code>Graphics</code>-object */ public void paintComponent(Graphics graphics) { super.paintComponent(graphics); processOffscreenImage(); // we need to create a copy of the given graphics since we // change the clip. Otherwise the border wouldn't be painted // properly (not at all in this case). Graphics2D graphics2D = (Graphics2D) graphics.create(); graphics2D.setClip(paintArea.x, paintArea.y, paintArea.width, paintArea.height); graphics2D.drawImage(offscreenImage, paintArea.x + offscreenOffset.x, paintArea.y + offscreenOffset.y, this); graphics2D.dispose(); } /** * Makes sure that the private attributes size, paintArea, offscreenOffset and * translated areas have sane values. It's called on component-resize. */ private void calculateAttributes() { size = getSize(size); paintArea.x = insets.left; paintArea.y = insets.top; paintArea.width = size.width - insets.left - insets.right; paintArea.height = size.height - insets.top - insets.bottom; int loOffscreenOffsetX = (paintArea.width - preferredSize.width) / 2; int loOffscreenOffsetY = (paintArea.height - preferredSize.height) / 2; if(loOffscreenOffsetX < 0) { loOffscreenOffsetX = 0; } if(loOffscreenOffsetY < 0) { loOffscreenOffsetY = 0; } offscreenOffset.x = loOffscreenOffsetX; offscreenOffset.y = loOffscreenOffsetY; translatedScrollArea.x = maxScrollArea.x + offscreenOffset.x; translatedScrollArea.y = maxScrollArea.y + offscreenOffset.y; translatedScrollArea.width = maxScrollArea.width; translatedScrollArea.height = maxScrollArea.height; translatedBackgroundImageArea.x = backgroundImageArea.x + offscreenOffset.x; translatedBackgroundImageArea.y = backgroundImageArea.y + offscreenOffset.y; translatedBackgroundImageArea.width = backgroundImageArea.width; translatedBackgroundImageArea.height = backgroundImageArea.height; repaint(); } /** * This methods takes the insets (the border) of this component into account * when the preferred size is calculated. Any border will work. It is called by * the property-change-listener if the border was changed. */ protected void calculatePreferredSize() { insets = getInsets(insets); preferredSize.width = insets.left + insets.right + backgroundImageArea.width; preferredSize.height = insets.top + insets.bottom + backgroundImageArea.height; setPreferredSize(preferredSize); invalidate(); } /** * This method requests a repaint of the scroll-area. The rest of the component * will not be repainted. It is called by <code>setScrollPosition()</code> . */ private void repaintScrollArea() { //setPainted(false); repaint(scrollArea.x + offscreenOffset.x, scrollArea.y + offscreenOffset.y, scrollArea.width, // + 1, scrollArea.height);// + 1 ); } /** * This method calls <code>super.addNotify()</code> and notifies the * scroll-thread by calling <code>setScrolling(true)</code>. It also * (re)initializes the scroll-position to MinimumScrollPosition (this is always * the negative height of the scroll-rectangle) and registers tbis component at * the <code>ToolTipManager</code>. * * @see #setScrolling * @see #setScrollPosition * @see #getMinimumScrollPosition */ public void addNotify() { super.addNotify(); setScrolling(true); ToolTipManager.sharedInstance().registerComponent(this); } /** * This method calls <code>super.removeNotify()</code> and sends the * scroll-thread into a wait-state by calling <code>setScrolling(false)</code> * . It also unregisters this component from the <code>ToolTipManager</code>. * * @see #setScrolling */ public void removeNotify() { super.removeNotify(); setScrolling(false); ToolTipManager.sharedInstance().unregisterComponent(this); // flush used buffer-images. flushOffscreenImage(); flushScrollImage(); } /** * This method is used to set the scrolling-property of this component. A value * of <code>true</code> will notify the scroll-thread that it has to resume * work. A value of <code>false</code> will send it into wait-state instead. * * @param scrolling The new scrolling value */ public void setScrolling(boolean scrolling) { if(this.scrolling != scrolling) { this.scrolling = scrolling; if(this.scrolling) { timer.start(); if(logger.isInfoEnabled()) logger.info("Timer started."); } else { timer.stop(); if(logger.isInfoEnabled()) logger.info("Timer stopped."); } } } /** * This method returns <code>true</code> if scrolling is currently active. If * it returns <code>false</code> then the scroll-thread is waiting. * * @return The scrolling value */ public boolean isScrolling() { return scrolling; } /** * Description of the Class * * @author Joern Huxhorn */ class AboutComponentListener extends ComponentAdapter { /** * Description of the Method * * @param e Description of the Parameter */ public void componentResized(ComponentEvent e) { AboutPanel.this.calculateAttributes(); } } /** * Description of the Class * * @author Joern Huxhorn */ class AboutPropertyChangeListener implements PropertyChangeListener { /** * Description of the Method * * @param evt Description of the Parameter */ public void propertyChange(PropertyChangeEvent evt) { String propertyName = evt.getPropertyName(); if("border".equals(propertyName)) { calculatePreferredSize(); } else if("foreground".equals(propertyName)) { flushScrollImage(); } else if("background".equals(propertyName)) { flushScrollImage(); } } } /** * This <code>MouseInputListener</code> handles the pause/resume on click as * well as the dragging inside the scroll-area. * * @author Joern Huxhorn */ class AboutMouseInputListener extends MouseInputAdapter { Point lastPoint = null; boolean scrollingBeforePress = false; boolean dragged = false; /** * Description of the Method * * @param evt Description of the Parameter */ public void mousePressed(MouseEvent evt) { if(handleMouseEvent(evt)) { // always stop scrolling if mouse is pressed inside // the scroll-area lastPoint = evt.getPoint(); scrollingBeforePress = isScrolling(); setScrolling(false); } else { lastPoint = null; } dragged = false; } /** * Description of the Method * * @param evt Description of the Parameter */ public void mouseReleased(MouseEvent evt) { if(dragged) { // set scrolling-attribute to the value before the user dragged. lastPoint = null; setScrolling(scrollingBeforePress); } } /** * Description of the Method * * @param evt Description of the Parameter */ public void mouseClicked(MouseEvent evt) { // this is only called after mouseReleased if no drag occurred. if(handleMouseEvent(evt)) { // toggle scrolling. setScrolling(!scrollingBeforePress); } dragged = false; } /** * Description of the Method * * @param evt Description of the Parameter */ public void mouseDragged(MouseEvent evt) { // only drag if original press was inside scroll-rectangle if(lastPoint != null) { dragged = true; Point currentPoint = evt.getPoint(); int yOffset = lastPoint.y - currentPoint.y; setScrollPosition(getScrollPosition() + yOffset); lastPoint = currentPoint; } } } private class TimerActionListener implements ActionListener { private final Logger logger = LoggerFactory.getLogger(AboutPanel.class); private long lastRepaintStart; private long frequency = 25; public void actionPerformed(ActionEvent e) { long currentTime = System.nanoTime() / 1000000; long meanTime = currentTime - lastRepaintStart; if(meanTime > frequency) { if(logger.isDebugEnabled()) logger.debug("Tick! meanTime={}", meanTime); increaseScrollPosition(); lastRepaintStart = currentTime; } } } }